﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Defender.AI.AStar
{
    public class PathFinder : IPathFinder
    {
        private List<PathFinderNode> mClose = new List<PathFinderNode>();
        private double mCompletedTime = 0.0;
        private bool mDebugFoundPath = false;
        private bool mDebugProgress = false;
        private bool mDiagonals = true;
        private HeuristicFormula mFormula = HeuristicFormula.Manhattan;
        private byte[,] mGrid = null;
        private bool mHeavyDiagonals = false;
        private int mHEstimate = 2;
        private int mHoriz = 0;
        private PriorityQueueB<PathFinderNode> mOpen = new PriorityQueueB<PathFinderNode>(new ComparePFNode());
        private bool mPunishChangeDirection = false;
        private int mSearchLimit = 0x7d0;
        private bool mStop = false;
        private bool mStopped = true;
        private bool mTieBreaker = false;

        public event PathFinderDebugHandler PathFinderDebug;

        public PathFinder(byte[,] grid)
        {
            if (grid == null)
            {
                throw new Exception("Grid cannot be null");
            }
            this.mGrid = grid;
        }

        public List<PathFinderNode> FindPath(NodePoint start, NodePoint end)
        {
            PathFinderNode node;
            sbyte[,] numArray;
            int num3;
            bool flag = false;
            int upperBound = this.mGrid.GetUpperBound(0);
            int num2 = this.mGrid.GetUpperBound(1);
            this.mStop = false;
            this.mStopped = false;
            this.mOpen.Clear();
            this.mClose.Clear();
            if (this.mDebugProgress && (this.PathFinderDebug != null))
            {
                this.PathFinderDebug(0, 0, start.X, start.Y, PathFinderNodeType.Start, -1, -1);
            }
            if (this.mDebugProgress && (this.PathFinderDebug != null))
            {
                this.PathFinderDebug(0, 0, end.X, end.Y, PathFinderNodeType.End, -1, -1);
            }
            if (this.mDiagonals)
            {
                numArray = new sbyte[,] { { 0, -1 }, { 1, 0 }, { 0, 1 }, { -1, 0 }, { 1, -1 }, { 1, 1 }, { -1, 1 }, { -1, -1 } };
            }
            else
            {
                numArray = new sbyte[,] { { 0, -1 }, { 1, 0 }, { 0, 1 }, { -1, 0 } };
            }
            node.G = 0;
            node.H = this.mHEstimate;
            node.F = node.G + node.H;
            node.X = start.X;
            node.Y = start.Y;
            node.PX = node.X;
            node.PY = node.Y;
            this.mOpen.Push(node);
            while ((this.mOpen.Count > 0) && !this.mStop)
            {
                node = this.mOpen.Pop();
                if (this.mDebugProgress && (this.PathFinderDebug != null))
                {
                    this.PathFinderDebug(0, 0, node.X, node.Y, PathFinderNodeType.Current, -1, -1);
                }
                if ((node.X == end.X) && (node.Y == end.Y))
                {
                    this.mClose.Add(node);
                    flag = true;
                    break;
                }
                if (this.mClose.Count > this.mSearchLimit)
                {
                    this.mStopped = true;
                    return null;
                }
                if (this.mPunishChangeDirection)
                {
                    this.mHoriz = node.X - node.PX;
                }
                num3 = 0;
                while (num3 < (this.mDiagonals ? 8 : 4))
                {
                    PathFinderNode node2;
                    node2.X = node.X + numArray[num3, 0];
                    node2.Y = node.Y + numArray[num3, 1];
                    if ((((node2.X >= 0) && (node2.Y >= 0)) && (node2.X < upperBound)) && (node2.Y < num2))
                    {
                        int num4;
                        if (this.mHeavyDiagonals && (num3 > 3))
                        {
                            num4 = node.G + ((int)(this.mGrid[node2.X, node2.Y] * 2.41));
                        }
                        else
                        {
                            num4 = node.G + this.mGrid[node2.X, node2.Y];
                        }
                        if (num4 != node.G)
                        {
                            if (this.mPunishChangeDirection)
                            {
                                if (((node2.X - node.X) != 0) && (this.mHoriz == 0))
                                {
                                    num4 += 20;
                                }
                                if (((node2.Y - node.Y) != 0) && (this.mHoriz != 0))
                                {
                                    num4 += 20;
                                }
                            }
                            int num5 = -1;
                            int num6 = 0;
                            while (num6 < this.mOpen.Count)
                            {
                                if ((this.mOpen[num6].X == node2.X) && (this.mOpen[num6].Y == node2.Y))
                                {
                                    num5 = num6;
                                    break;
                                }
                                num6++;
                            }
                            if ((num5 == -1) || (this.mOpen[num5].G > num4))
                            {
                                int num7 = -1;
                                for (num6 = 0; num6 < this.mClose.Count; num6++)
                                {
                                    if ((this.mClose[num6].X == node2.X) && (this.mClose[num6].Y == node2.Y))
                                    {
                                        num7 = num6;
                                        break;
                                    }
                                }
                                if ((num7 == -1) || (this.mClose[num7].G > num4))
                                {
                                    node2.PX = node.X;
                                    node2.PY = node.Y;
                                    node2.G = num4;
                                    switch (this.mFormula)
                                    {
                                        case HeuristicFormula.MaxDXDY:
                                            {
                                                int introduced26 = Math.Abs((int)(node2.X - end.X));
                                                node2.H = this.mHEstimate * Math.Max(introduced26, Math.Abs((int)(node2.Y - end.Y)));
                                                break;
                                            }
                                        case HeuristicFormula.DiagonalShortCut:
                                            {
                                                int introduced27 = Math.Abs((int)(node2.X - end.X));
                                                int num8 = Math.Min(introduced27, Math.Abs((int)(node2.Y - end.Y)));
                                                int introduced28 = Math.Abs((int)(node2.X - end.X));
                                                int num9 = introduced28 + Math.Abs((int)(node2.Y - end.Y));
                                                node2.H = ((this.mHEstimate * 2) * num8) + (this.mHEstimate * (num9 - (2 * num8)));
                                                break;
                                            }
                                        case HeuristicFormula.Euclidean:
                                            {
                                                double introduced29 = Math.Pow((double)(node2.X - end.X), 2.0);
                                                node2.H = (int)(this.mHEstimate * Math.Sqrt(introduced29 + Math.Pow((double)(node2.Y - end.Y), 2.0)));
                                                break;
                                            }
                                        case HeuristicFormula.EuclideanNoSQR:
                                            {
                                                double introduced30 = Math.Pow((double)(node2.X - end.X), 2.0);
                                                node2.H = (int)(this.mHEstimate * (introduced30 + Math.Pow((double)(node2.Y - end.Y), 2.0)));
                                                break;
                                            }
                                        case HeuristicFormula.Custom1:
                                            {
                                                NodePoint point = new NodePoint(Math.Abs((int)(end.X - node2.X)), Math.Abs((int)(end.Y - node2.Y)));
                                                int num10 = Math.Abs((int)(point.X - point.Y));
                                                int num11 = Math.Abs((int)(((point.X + point.Y) - num10) / 2));
                                                node2.H = this.mHEstimate * (((num11 + num10) + point.X) + point.Y);
                                                break;
                                            }
                                        default:
                                            {
                                                int introduced25 = Math.Abs((int)(node2.X - end.X));
                                                node2.H = this.mHEstimate * (introduced25 + Math.Abs((int)(node2.Y - end.Y)));
                                                break;
                                            }
                                    }
                                    if (this.mTieBreaker)
                                    {
                                        int num12 = node.X - end.X;
                                        int num13 = node.Y - end.Y;
                                        int num14 = start.X - end.X;
                                        int num15 = start.Y - end.Y;
                                        int num16 = Math.Abs((int)((num12 * num15) - (num14 * num13)));
                                        node2.H += (int)(num16 * 0.001);
                                    }
                                    node2.F = node2.G + node2.H;
                                    if (this.mDebugProgress && (this.PathFinderDebug != null))
                                    {
                                        this.PathFinderDebug(node.X, node.Y, node2.X, node2.Y, PathFinderNodeType.Open, node2.F, node2.G);
                                    }
                                    this.mOpen.Push(node2);
                                }
                            }
                        }
                    }
                    num3++;
                }
                this.mClose.Add(node);
                if (this.mDebugProgress && (this.PathFinderDebug != null))
                {
                    this.PathFinderDebug(0, 0, node.X, node.Y, PathFinderNodeType.Close, node.F, node.G);
                }
            }
            if (flag)
            {
                PathFinderNode node3 = this.mClose[this.mClose.Count - 1];
                for (num3 = this.mClose.Count - 1; num3 >= 0; num3--)
                {
                    if (((node3.PX == this.mClose[num3].X) && (node3.PY == this.mClose[num3].Y)) || (num3 == (this.mClose.Count - 1)))
                    {
                        if (this.mDebugFoundPath && (this.PathFinderDebug != null))
                        {
                            this.PathFinderDebug(node3.X, node3.Y, this.mClose[num3].X, this.mClose[num3].Y, PathFinderNodeType.Path, this.mClose[num3].F, this.mClose[num3].G);
                        }
                        node3 = this.mClose[num3];
                    }
                    else
                    {
                        this.mClose.RemoveAt(num3);
                    }
                }
                this.mStopped = true;
                return this.mClose;
            }
            this.mStopped = true;
            return null;
        }

        public void FindPathStop()
        {
            this.mStop = true;
        }

        [DllImport("KERNEL32.DLL", EntryPoint = "RtlZeroMemory")]
        public static extern bool ZeroMemory(byte[] destination, int length);

        public double CompletedTime
        {
            get
            {
                return this.mCompletedTime;
            }
            set
            {
                this.mCompletedTime = value;
            }
        }

        public bool DebugFoundPath
        {
            get
            {
                return this.mDebugFoundPath;
            }
            set
            {
                this.mDebugFoundPath = value;
            }
        }

        public bool DebugProgress
        {
            get
            {
                return this.mDebugProgress;
            }
            set
            {
                this.mDebugProgress = value;
            }
        }

        public bool Diagonals
        {
            get
            {
                return this.mDiagonals;
            }
            set
            {
                this.mDiagonals = value;
            }
        }

        public HeuristicFormula Formula
        {
            get
            {
                return this.mFormula;
            }
            set
            {
                this.mFormula = value;
            }
        }

        public bool HeavyDiagonals
        {
            get
            {
                return this.mHeavyDiagonals;
            }
            set
            {
                this.mHeavyDiagonals = value;
            }
        }

        public int HeuristicEstimate
        {
            get
            {
                return this.mHEstimate;
            }
            set
            {
                this.mHEstimate = value;
            }
        }

        public bool PunishChangeDirection
        {
            get
            {
                return this.mPunishChangeDirection;
            }
            set
            {
                this.mPunishChangeDirection = value;
            }
        }

        public int SearchLimit
        {
            get
            {
                return this.mSearchLimit;
            }
            set
            {
                this.mSearchLimit = value;
            }
        }

        public bool Stopped
        {
            get
            {
                return this.mStopped;
            }
        }

        public bool TieBreaker
        {
            get
            {
                return this.mTieBreaker;
            }
            set
            {
                this.mTieBreaker = value;
            }
        }

        internal class ComparePFNode : IComparer<PathFinderNode>
        {
            public int Compare(PathFinderNode x, PathFinderNode y)
            {
                if (x.F > y.F)
                {
                    return 1;
                }
                if (x.F < y.F)
                {
                    return -1;
                }
                return 0;
            }
        }
    }
}
